﻿using System;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows.Input;
using System.Windows.Threading;

namespace xCarLinkMP3Organizer
{
    public class ViewModel : BaseViewModel
    {
        private readonly ObservableCollection<object> _discs;
        private readonly ObservableCollection<UsbDrive> _drives;
        private readonly UsbDrives _usbManager;
        private UsbDrive _activeDrive;
        private DiscViewModel _winampActive;

        private FSWWrapper _fswRoot;

        private readonly DispatcherTimer _dtimer;

        private ICommand _addCmd;

        public ViewModel()
        {
            _usbManager = new UsbDrives();
            var dispatcher = Dispatcher.CurrentDispatcher;
            _usbManager.StateChanged +=
                (sender, args) => dispatcher.BeginInvoke(
                    (EventHandler<UsbStateChangedEventArgs>) UsbManagerStateChanged,
                    DispatcherPriority.Normal, sender, args);

            _drives = new ObservableCollection<UsbDrive>(_usbManager.GetAvailable());

            _discs = new ObservableCollection<object>();

            ActiveDrive = _drives.FirstOrDefault();

            _dtimer = new DispatcherTimer(
                TimeSpan.FromMilliseconds(500),
                DispatcherPriority.ApplicationIdle,
                Delayed, Dispatcher.CurrentDispatcher) {IsEnabled = false};
        }

        private void Delayed(object sender, EventArgs eventArgs)
        {
            if (Controller.Instance.IsBusy)
                return;

            if (ActiveDrive != null)
                _usbManager.UpdateInfo(ActiveDrive);
            _dtimer.IsEnabled = false;
        }

        public void RequestUpdateDiskInfo()
        {
            _dtimer.IsEnabled = true;
        }

        public ICommand AddCmd
        {
            get { return _addCmd ?? (_addCmd = new LambdaViewCommmand(AddFromMenu)); }
        }

        private void AddFromMenu(object obj)
        {
            if (ActiveDrive == null)
                return;

            var num = Enumerable.Range(1, _discs.Count)
                .Where(i => _discs
                                .Select(d => d as DiscViewModel)
                                .Where(d => d != null && d.Number == i).FirstOrDefault() == null)
                .First();

            Directory.CreateDirectory(ActiveDrive.Name + "\\CD" + num.ToString("00"));
        }

        public ObservableCollection<UsbDrive> Drives
        {
            get { return _drives; }
        }

        public UsbDrive ActiveDrive
        {
            get { return _activeDrive; }
            set
            {
                if (_activeDrive == value)
                    return;

                _activeDrive = value;
                PropChanged("ActiveDrive");

                OnActiveDriveChanged();
            }
        }

        public ObservableCollection<object> Discs
        {
            get { return _discs; }
        }

        public DiscViewModel WinampActive
        {
            get { return _winampActive; }
            set
            {
                if (_winampActive != null)
                {
                    _winampActive.IsOwnWinamp = false;
                }

                _winampActive = value;

                if (_winampActive != null)
                {
                    _winampActive.IsOwnWinamp = true;
                }
            }
        }

        private void UsbManagerStateChanged(object sender, UsbStateChangedEventArgs e)
        {
            if (e.State == UsbStateChange.Added)
            {
                var drive = new UsbDrive(e.Name);
                _usbManager.UpdateInfo(drive);
                _drives.Add(drive);

                if (ActiveDrive == null)
                    ActiveDrive = drive;
            }
            else if (e.State == UsbStateChange.Removed)
            {
                int? idx = _drives.Select((d, i) => new {d, i = (int?) i})
                    .Where(d => d.d.Name == e.Name)
                    .Select(d => d.i).FirstOrDefault();
                if (idx.HasValue)
                    _drives.RemoveAt(idx.Value);
            }
        }

        private void OnActiveDriveChanged()
        {
            foreach (BaseViewModel d in _discs.ToList())
            {
                d.Dispose();
                _discs.Remove(d);
            }

            if (_activeDrive != null)
            {
                foreach (string dir in Directory.EnumerateDirectories(_activeDrive.Name + "\\").OrderBy(s => s))
                {
                    int? num = GetDiscNumber(dir);
                    if (num != null)
                    {
                        AddDisc(dir);
                    }
                }

                _discs.Add(new FakeDiscViewModel(this));
            }

            CreateWatcher(_activeDrive);
        }

        private void CreateWatcher(UsbDrive disk)
        {
            if (_fswRoot != null)
            {
                _fswRoot.EnableRaisingEvents = false;
                _fswRoot.Created -= OnDirCreated;
                _fswRoot.Renamed -= OnDirRenamed;
                _fswRoot.Deleted -= OnDirDeleted;
                _fswRoot.Dispose();
                _fswRoot = null;
            }

            if (disk != null)
            {
                _fswRoot = new FSWWrapper(disk.Name + "\\");
                _fswRoot.Created += OnDirCreated;
                _fswRoot.Renamed += OnDirRenamed;
                _fswRoot.Deleted += OnDirDeleted;
                _fswRoot.EnableRaisingEvents = true;
            }
        }

        private void OnDirDeleted(object sender, FileSystemEventArgs e)
        {
            RemoveDisc(e.FullPath);
            RequestUpdateDiskInfo();
        }

        private void OnDirRenamed(object sender, RenamedEventArgs e)
        {
            RemoveDisc(e.OldFullPath);
            AddDisc(e.FullPath);
            RequestUpdateDiskInfo();
        }

        private void OnDirCreated(object sender, FileSystemEventArgs e)
        {
            AddDisc(e.FullPath);
            RequestUpdateDiskInfo();
        }

        private void RemoveDisc(string path)
        {
            foreach (object d in _discs.ToArray())
            {
                var disc = d as DiscViewModel;
                if (disc == null)
                    continue;

                if (disc.Path == path)
                {
                    disc.Dispose();
                    _discs.Remove(d);
                    break;
                }
            }
        }

        private void AddDisc(string fullPath)
        {
            int? num = GetDiscNumber(fullPath);
            if (num == null)
                return;

            var dvm = new DiscViewModel(this, ActiveDrive, num.Value);

            var i = Array.BinarySearch(_discs.ToArray(), dvm);
            if (i < 0)
                i = ~i;
            _discs.Insert(i, dvm);
        }

        private int? GetDiscNumber(string fullDirName)
        {
            Match match = Regex.Match(fullDirName, @"^\w:\\CD(?<num>\d\d)$");
            if (match.Success)
                return Int32.Parse(match.Groups["num"].Value);

            return null;
        }
    }

    public class FakeDiscViewModel : BaseViewModel, IComparable
    {
        public ICommand NewCdCmd { get; private set; }

        public FakeDiscViewModel(ViewModel parent)
        {
            NewCdCmd = parent.AddCmd;
        }

        public int CompareTo(object obj)
        {
            return 1;
        }
    }
}